#region License
// Product:
// MCM (Message-Command-Message)
// mcm-framework.net
// 
// Author:
// Pawel Idzikowski
// Cologne, Germany
// idzikowski@polenter.com
// 
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the author(s) be held liable for any damages arising from
// the use of this software.
// 
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 
//     1. The origin of this software must not be misrepresented; you must not
//       claim that you wrote the original software. If you use this software
//       in a product, an acknowledgment in the product documentation would be
//       appreciated but is not required.
// 
//     2. Altered source versions must be plainly marked as such, and must not
//       be misrepresented as being the original software.
// 
//     3. This notice may not be removed or altered from any source distribution.
// 
#endregion
using System;
using System.Runtime.CompilerServices;
using Polenter.Mcm.Advanced;

namespace Polenter.Mcm
{
    ///<summary>
    /// Queues messages on an internal thread and brodcasts them one after another on this thread.
    ///</summary>
    public class MessageChannel : IDisposable
    {
        ///<summary>
        /// Default name for the trace log
        ///</summary>
        public const string TraceLogName = "MessageChannel";

        private readonly McmLog _log;
        /// <summary>
        /// Internal queue queueing items asynchronously and
        /// dequeing them on the internal thread.
        /// </summary>
        private readonly AsyncQueue<object> _queue;

        private event MessageEventHandler _messageArrived;

        ///<summary>
        ///</summary>
        ///<param name="name">must be the same as in the MessageSubscriberAttribute</param>
        public MessageChannel(string name)
        {
            if (name == null) throw new ArgumentNullException("name");
            _queue = new AsyncQueue<object>(name);
            _queue.ItemDequeued += on_queue_ItemDequeued;
            _log = new McmLog(TraceLogName);
        }

        ///<summary>
        ///</summary>
        ///<param name="name">must be the same as in the MessageSubscriberAttribute</param>
        ///<param name="queue">custom queue, eg. MSMQ or a persisted one</param>
        public MessageChannel(string name, IQueue queue)
        {
            if (name == null) throw new ArgumentNullException("name");
            _queue = new AsyncQueue<object>(name, queue);
            _queue.ItemDequeued += on_queue_ItemDequeued;
            _log = new McmLog(TraceLogName);
        }

        ///<summary>
        /// must be the same as in the MessageSubscriberAttribute
        ///</summary>
        public string Name
        {
            get { return _queue.Name; }
        }

        ///<summary>
        /// Get the working status of the queue
        ///</summary>
        internal bool IsOpened
        {
            get { return _queue.IsWorking; }
        }

        #region IDisposable Members

        ///<summary>
        /// Closes the message queue.
        ///</summary>
        public void Dispose()
        {
            Close();
        }

        #endregion

        private void on_queue_ItemDequeued(object sender, AsyncQueueEventArgs<object> e)
        {
            try
            {
                var mes = new MessageEventArgs(e.Item);
                mes.MessageChannel = this;
                invokeMessageArrived(mes);
            }
            catch (Exception ex)
            {
                _log.WriteError(ex);
                throw;
            }
        }

        /// <summary>
        /// Adds asynchronously the message to the queue.
        /// The message will be dequed on an internal thread
        /// </summary>
        /// <param name="message"></param>
        public void Post(object message)
        {
            if (message == null) throw new ArgumentNullException("message");
            _log.WriteInfo(string.Format("{0}.PostMessage({1})", _queue.Name, message));
            _queue.Enqueue(message);
        }

        /// <summary>
        /// Starts the message queue
        /// </summary>
        internal void Open()
        {
            _log.WriteInfo(string.Format("{0}.Open()", _queue.Name));
            _queue.Start();
        }

        ///<summary>
        /// Stops the message queue
        ///</summary>
        internal void Close()
        {
            _log.WriteInfo(string.Format("{0}.Close()", _queue.Name));
            _queue.Stop();
        }

        ///<summary>
        /// Clears the buffer and returns all not dequed messages
        ///</summary>
        ///<returns></returns>
        public object[] Clear()
        {
            return _queue.Clear();
        }

        ///<summary>
        /// Returns all not dequed messages from the queue.
        ///</summary>
        ///<returns></returns>
        public object[] ToArray()
        {
            return _queue.ToArray();
        }

        ///<summary>
        /// Invoked when a posted message is dequed from the internal queue.
        /// Internal queue runs on a private thread. Message on this thead are
        /// dequed in the order they were posted.
        /// Only one message at a time is dequed.
        ///</summary>
        public event MessageEventHandler MessageArrived
        {
            add { subscribeMessageArrived(value); }
            remove { unsubscribeMessageArrived(value); }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void subscribeMessageArrived(MessageEventHandler value)
        {
            _messageArrived += value;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void unsubscribeMessageArrived(MessageEventHandler value)
        {
            _messageArrived -= value;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void invokeMessageArrived(MessageEventArgs e)
        {
            _log.WriteInfo(string.Format("{0}.MessageArrived({1})", _queue.Name, e.Message));
            MessageEventHandler handler = _messageArrived;
            if (handler != null) handler(this, e);
        }
    }
}